' Written by James Deakins, March 2013
' Runs under DOS and regular MMBasic.
' Written using MMBasic V4.3a. 

' This library reads values from a configuration file.  If the file
' does not exist, it is created and the user is prompted for the values.

' You can allocate a default config file name or leave it blank.
' If blank, the code will try to read the last filename from the
' MM.FNAME$ variable.  If there isn't one there either, the code
' will continue to prompt for a valid file name until it gets one.

' All variables allocated by this program are local, so they will not 
' affect the calling program if the CHAIN command is used. 
' The global variables that you set in function ReadValues will
' be usable by  the calling program.

' If you use this library, you will have to populate the ReadValues
' routine with appropriate code to process the parameters and values
' that will be read from the config file.

' Entries in the config file will be in the format
' parameter=value   NOTE: starts 1st column, and no spaces on either 
'                         side of the = sign.
' ' comments        NOTE: start with a single quote in the 1st column.

' Examples
' ' This parameter file holds 3 parameters and their values to support
' ' program xxx.bas.  If you edit this file, all entries start at the 
' ' first column, and there are no spaces on either side of the = sign.
' City=Bangkok
' PostalCode=101010
' Population=000112233
'----------------------------------------------------------------------'
' program is called twice - once to read config file, once to update it.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

' Main

IF bReadConfig THEN
  ReadConfigFile(ConfFileName$)
ELSE
  WriteConfigFile
ENDIF

' memory

UpTo = UpTo + 1
CHAIN MasterFile$
END

' End Main

'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Subroutine
' This subroutine reads the user preferences from the configuration
' file.  If the file does not exist, it is created.
' 
' We need two different ways of testing for the file's existance
' because MMBasic only returns valid Error Numbers for files on the
' A: or B: drives.  It does not return a useful error number if 
' the file does not exist in DOS.
' 
' Unless specified otherwise, the Config file is the name of the 
' calling program -.BAS +.CFG
'
' The name of the Configuration file is retained in global
' variable ConfFile$.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
SUB ReadConfigFile(ConfFileName$)
 
LOCAL Right4$, pFileName$, bValidFileName, LineIn$, ptr, Param$, Value$
LOCAL LineCount, ParamCount, CommCount, BadCount

pFileName$ = ConfFileName$    ' so we don't overwrite the original

IF pFileName$ = "" THEN    ' user has not given a default name to the config file
  pFileName$ = MM.FNAME$   ' so see if the user loaded or saved code as a file name
ENDIF

DO WHILE pFileName$ = ""   ' no default, and no obvious file name, so ask for one
  INPUT "What filename do you want to use for the config file? "; pFileName$
LOOP

' strip off the .bas and add .cfg.  If already .cfg, leave as is. If no extension, add .cfg.

DO 
  bValidFileName = True
  Right4$ = UCASE$(RIGHT$(pFileName$,4))
  IF Right4$ = ".BAS" THEN
    ConfFile$ = LEFT$(pFileName$, LEN(pFileName$) - 4) + ".cfg" ' replace .bas with .cfg
  ELSEIF Right4$ = ".CFG" THEN
    ConfFile$ = pFileName$                                      ' no need to change anything
  ELSEIF INSTR(1,pFileName$,".") = 0 THEN                       ' just a file name, no extension
    ConfFile$ = pFileName$ + ".cfg"
  ELSE
    PRINT "Please give a valid name for your configuration file (with or without an extension)."
    PRINT "You can even give the name of the basic file that has (or needs) configuration data,"
    PRINT "in which case it will use the same file name, but with the .bas extension changed to .cfg."
    PRINT "eg, for a basic program called CONFIG.BAS, supply CONFIG or CONFIG.CFG or CONFIG.BAS"
    PRINT "You can also use multiple configuration file names if you want, like CONFIG1, CONFIG2."    
    PRINT
    pFileName$ = ""
    DO WHILE pFileName$ = ""
      "What filename do you want to use for the config file? "; pFileName$
    LOOP
    bValidFileName$ = False                                     ' force another pass through
  ENDIF 
LOOP UNTIL bValidFileName

' Open and read the Config file if it exists.  If it doesn't exist, create it, then open and read it.  

IF NOT FileExists(ConfFile$) THEN
   ? "File ";ConfFile$; " does NOT exist"
   ?
   Open ConfFile$ for output as #8
   ' Prompt for the values and create the config file.  Allocate to global variables.
   ? "Please enter your parameter name and the value in the format: "
   ? "parameter=value"
   ? "Continue to enter parameter and value pairs, one pair per line until finished," 
   ? "then just press the Enter key by itself." 
   ? "Comments can also be entered, but they MUST start with a single quote (') "
   ? "as the first character." 
   ? "If you make a mistake, edit the config file with a text editor."
   ?
   LINE INPUT "Next line? - or Enter to finish ", LineIn$
   DO WHILE LineIn$ <> ""
     PRINT #8, LineIn$
     LINE INPUT "Next line? - or Enter to finish ", LineIn$
   LOOP
   ? "Finished entering parameters.  Closing configuration file."  
   CLOSE #8  
ENDIF


bOK        = True
LineCount  = 0
BadCount   = 0
ParamCount = 0
CommCount  = 0

' Read in the values - allocate to global variables
Open ConfFile$ for input as #8
DO WHILE NOT EOF(#8)
  LineCount = LineCount + 1
  LINE INPUT #8, LineIn$
  IF LEFT$(LineIn$,1) = "'" THEN
    CommCount = CommCount + 1
  ELSE
    ptr = INSTR(1, LineIn$, "=")
    Param$ = LEFT$(LineIn$,ptr-1)
    IF ptr = LEN(LineIn$) THEN 
      Value$ = ""                 ' an empty value field
    ELSE
      Value$ = MID$( LineIn$,ptr+1)
    ENDIF
    'the programmer will have to write matching code in function ReadValues
    IF NOT ReadValues(Param$, Value$) THEN ' returns False if there was a problem
      BadCount = BadCount + 1
    ENDIF
    ParamCount = ParamCount + 1
  ENDIF
LOOP

' may as well get the English right...

?
? "Finished reading"; LineCount ; 
IF LineCount = 1 then
  ? " line from "; ConfFile$ 
ELSE
  ? " lines from "; ConfFile$
ENDIF

IF CommCount = 1 THEN
  ? "There was"; CommCount; " comment, and"; 
ELSE
  ? "There were"; CommCount; " comments, and"; 
ENDIF

IF ParamCount = 1 THEN
  ? ParamCount; " parameter was processed";
ELSE
  ? ParamCount; " parameters were processed";
ENDIF

IF BadCount > 0 THEN
    ?
    ? "of which"; BadCount; " parameters could not be processed correctly."
    ?
    ? "You will have to edit the code in function ReadValues to process the unknown parameters."
    ? "--------------------------------------------------------------------------------------------"
    END ' no point continuing if parameters were unable to be assigned correctly
ELSE
    ? "."
    ?
    ?
ENDIF

CLOSE #8  

END SUB ' ReadConfigFile

'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Subroutine
' This subroutine writes the user preferences to the configuration
' file.  
'
' We've already worked out the configuration file name and stored
' it in variable ConfFile$.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
SUB WriteConfigFile(ConfFileName$)

Local LineCount, LineIn$, CommCount

OPEN ConfFile$ for INPUT as #8
OPEN "config.tmp" for OUTPUT as #7

DO WHILE NOT EOF(#8)
  LineCount = LineCount + 1
  LINE INPUT #8, LineIn$
  IF LEFT$(LineIn$,1) = "'" THEN
    CommCount = CommCount + 1
    PRINT #7, LineIn$             ' write it to the file unchanged
  ELSE
    ptr = INSTR(1, LineIn$, "=")
    Param$ = LEFT$(LineIn$,ptr-1)
    IF ptr = LEN(LineIn$) THEN 
      Value$ = ""                 ' an empty value field
    ELSE
      Value$ = MID$( LineIn$,ptr+1)
    ENDIF
    'the programmer will have to write matching code in function WriteValues
    IF NOT WriteValues(Param$, Value$) THEN ' returns False if there was a problem
      BadCount = BadCount + 1
    ENDIF
    ParamCount = ParamCount + 1
  ENDIF
LOOP

Close #7, #8
COPY "config.tmp" TO ConfFile$        ' overwrite the old Config file with the new file
KILL "config.tmp"

' may as well get the English right...

?
? "Finished writing"; LineCount ; 
IF LineCount = 1 then
  ? " line to "; ConfFile$ 
ELSE
  ? " lines to "; ConfFile$
ENDIF

IF CommCount = 1 THEN
  ? "There was"; CommCount; " comment, and"; 
ELSE
  ? "There were"; CommCount; " comments, and"; 
ENDIF

IF ParamCount = 1 THEN
  ? ParamCount; " parameter was processed";
ELSE
  ? ParamCount; " parameters were processed";
ENDIF

IF BadCount > 0 THEN
    ?
    ? "of which"; BadCount; " parameters could not be processed correctly."
    ?
    ? "You will have to edit the code in function WriteValues to process the unknown parameters."
    ? "-----------------------------------------------------------------------------------------"
    END ' no point continuing if parameters were unable to be written correctly
ELSE
    ? "."
    ?
    ?
ENDIF

END SUB 'WriteConfigFile


''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Function
' Check whether a file exists.
' Returns a Boolean True or False.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Uses two different methods to check for a file's existence,
' depending on whether the code is run under DOS, or on a 
' Maximite.
' Versions of MMBasic prior to 4.3a returned "" in MM.DEVICE$
' when run in the DOS version.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
Function fileExists(FileName$)

Local Answer$, bFileExists

IF (MM.DEVICE$ = "DOS") THEN 
  open "exist.bat" for output as #8
  Print #8, "if exist " + chr$(34) + FileName$ + chr$(34) + " ( "
  Print #8, "   echo YES> exist.tmp"
  Print #8, ") Else ("
  Print #8, "   echo NO> exist.tmp"
  Print #8, ")"
  Print #8, "Exit" 
  Close #8

  ' Run batch that redirects DIR results to exist.tmp file.
  ' Redirect all messages from the batch file to nul (ie, not displayed)
  SYSTEM "exist.bat > nul"

  ' Read the single line from exist.tmp for the answer as to whether the file exists.
  Open "exist.tmp" for input as #8
  Line Input #8, Answer$
  bFileExists = (Answer$ = "YES")
  Close #8
  KILL "exist.bat"  ' and clean up 
  KILL "exist.tmp"  
ELSE      ' running on a Maximite or similar hardware   
' Turn off Abort on Error so we can capture the Error Number
  Option Error Continue
  Open FileName$ for Input as #8 

  IF MM.ERRNO = 6 THEN ' can't find file
    bFileExists = False
  ELSEIF MM.ERRNO = 0 THEN
    bFileExists = True
    CLOSE #8
  ELSE
    ? "ERROR: the Existence Check for file "; ConfFile$; " returned MM.ERRNO "; MM.ERRNO
    ? "The description of MM.ERRNO"; MM.ERRNO; " is: "; ErrNoDesc$(MM.ERRNO)
    ' Close #8
    Option Error Abort
    END
  ENDIF
  Option Error Abort
ENDIF

fileExists = bFileExists
 
END FUNCTION ' fileExists

'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Function
' Return the matching Description for an MMBasic 
' Error Number.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' The descriptions  were taken from the V4.3 manual.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'       
Function ErrNoDesc$(ErrNo)
  
Local Desc$
 
IF ErrNo = 0 THEN
  Desc$ = "No error"
ELSEIF ErrNo = 1 THEN
  Desc$ = "No SD card found"
ELSEIF ErrNo = 2 THEN
  Desc$ = "SD card is write protected"
ELSEIF ErrNo = 3 THEN
  Desc$ = "Not enough space"
ELSEIF ErrNo = 4 THEN
  Desc$ = "All root directory entries are taken"
ELSEIF ErrNo = 5 THEN
  Desc$ = "Invalid filename"
ELSEIF ErrNo = 6 THEN
  Desc$ = "Cannot find file"
ELSEIF ErrNo = 7 THEN
  Desc$ = "Cannot find directory"
ELSEIF ErrNo = 8 THEN
  Desc$ = "File is read only"
ELSEIF ErrNo = 9 THEN
  Desc$ = "Cannot open file"
ELSEIF ErrNo = 10 THEN
  Desc$ = "Error reading from file"
ELSEIF ErrNo = 11 THEN
  Desc$ = "Error writing to file"
ELSEIF ErrNo = 12 THEN
  Desc$ = "Not a file"
ELSEIF ErrNo = 13 THEN
  Desc$ = "Not a directory"
ELSEIF ErrNo = 14 THEN
  Desc$ = "Directory not empty"
ELSEIF ErrNo = 15 THEN
  Desc$ = "Hardware error accessing the storage media"
ELSEIF ErrNo = 16 THEN
  Desc$ = "Flash memory write failure"
ELSE
  Desc$ = "******** " + ErrNo + " is an invalid Error Number iaw V4.3 manual **********"
ENDIF       
 
ErrNoDesc$ = Desc$

END FUNCTION ' ErrNoDesc$ 

'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Function ReadValues
' Process the values read from the config file.
' Allocates values to global variables depending on the
' values of param$ and value$ passed in.
' Returns a False if a param cannot be found in the IF 
' statements.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
FUNCTION ReadValues(Param$, Value$)

LOCAL UCParam$, bOK

bOK = True

UCParam$ = UCASE$(Param$)

' Use an IF statement to process each possible parameter.

IF UCParam$ = "BDEBUG" THEN
  bDebug = (UCASE$(Value$) = "TRUE")
ELSEIF UCParam$ = "LASTSTART" THEN
  lastStart = VAL(Value$)
ELSEIF UCParam$ = "LASTFIRST" THEN
  lastFirst = VAL(Value$)
ELSEIF UCParam$ = "LASTINCREMENT" THEN
  lastIncrement = VAL(Value$)
ELSEIF UCParam$ = "LASTOPEN" THEN
  lastOpen$ = Value$
ELSEIF UCParam$ = "LASTOUT" THEN
  lastOut$ = Value$
ELSEIF UCParam$ = "LASTBINCREMENTONBLANK" THEN
  lastbIncrementOnBlank = (UCASE$(Value$) = "TRUE")
ELSEIF UCParam$ = "LASTBINSERTLINENUMWHENBLANK" THEN
  lastbInsertLineNumWhenBlank = (UCASE$(Value$) = "TRUE")
ELSEIF UCParam$ = "LASTBCALLFORMAT" THEN
  lastbCallFormat = (UCASE$(Value$) = "TRUE")
ELSEIF UCParam$ = "LASTFORMATBASPARAMETERS" THEN
  lastFormatBasParameters$ = Value$
ELSE
  PRINT "Unknown parameter and its value "; Param$; "="; Value$
  bOK = False
ENDIF

ReadValues = bOK

END FUNCTION ' ReadValues 


'
'''''''''''''''''''''''''''''''''''''''''''''''''''
' Function WriteValues
' Read values of Global Variables and store then in
' a configuration file.
' Returns a False if a param cannot be found in the 
' IF statements.
'''''''''''''''''''''''''''''''''''''''''''''''''''
FUNCTION WriteValues(Param$, Value$)
' Value$ is only used when displaying an error.

LOCAL UCParam$, bOK, Out$

bOK = True

UCParam$ = UCASE$(Param$)

IF UCParam$ = "BDEBUG" THEN
  Out$ = "bDebug="
  IF bDebug THEN
    Out$ = Out$ + "TRUE"
  ELSE
    Out$ = Out$ + "FALSE"
  ENDIF
ELSEIF UCParam$ = "LASTSTART" THEN
  Out$ = "lastStart=" + STR$(lastStart)
ELSEIF UCParam$ = "LASTFIRST" THEN
  Out$ = "lastFirst=" + STR$(lastFirst)
ELSEIF UCParam$ = "LASTINCREMENT" THEN
  Out$ = "lastIncrement=" + STR$(lastIncrement)
ELSEIF UCParam$ = "LASTOPEN" THEN
  Out$ = "lastOpen=" + lastOpen$
ELSEIF UCParam$ = "LASTOUT" THEN
  Out$ = "lastOut=" + lastOut$
ELSEIF UCParam$ = "LASTBINCREMENTONBLANK" THEN
  Out$ = "lastbIncrementOnBlank="
  IF lastbIncrementOnBlank THEN
    Out$ = Out$ + "TRUE"
  ELSE
    Out$ = Out$ + "FALSE"
  ENDIF
ELSEIF UCParam$ = "LASTBINSERTLINENUMWHENBLANK" THEN
  Out$ = "lastbInsertLineNumWhenBlank="
  IF lastbInsertLineNumWhenBlank THEN
    Out$ = Out$ + "TRUE"
  ELSE
    Out$ = Out$ + "FALSE"
  ENDIF
  lastbCallFormat=TRUE
ELSEIF UCParam$ = "LASTBCALLFORMAT" THEN
  Out$ = "lastbCallFormat="
  IF lastbCallFormat THEN
    Out$ = Out$ + "TRUE"
  ELSE
    Out$ = Out$ + "FALSE"
  ENDIF
ELSEIF UCParam$ = "LASTFORMATBASPARAMETERS" THEN
  IF bCallFormat THEN   
    Out$ = "lastFormatBasParameters=" + lastFormatBasParameters$
  ELSE
    Out$ = "lastFormatBasParameters="
  ENDIF
ELSE
  PRINT  "Unknown parameter and its value "; Param$; "="; Value$
  bOK = False
ENDIF

IF bOK THEN 
  PRINT #7, Out$
ENDIF

WriteValues = bOK

END FUNCTION ' WriteValues
